<?php
/**
 * Memcached 行数据缓存
 * 
 * 该类不能用new实例化 所有方法使用静态调用
 * 
 * @author fanzhanao <jw0603@qq.com>
 * @version $Id: MemRow.php 4766 2010-04-09 06:23:04Z fanzhanao $
 * @package MemRow
 */
class MemRow extends MemcacheRow
{
	/**
	 * 缓存开关
	 * 
	 * 设置为false开启缓存功能，true为关闭缓存功能
	 * 
	 * 该设置是针对memcache server挂机时候的设置，
	 * 设置为true将关闭缓存功能，减少挂机时候连接memcache server的消耗,同时所有查询将通过数据库动态查询
	 *
	 * @var bool
	 */
	private static $_closeCache = false;
	
	/**
	 * id 列表缓存时间
	 * 
	 * 默认为最大缓存时间 如果要修改id 列表缓存时间需要在进行缓存操作前先设置该属性。
	 * 缓存10分钟：
	 * MemRow::setListCacheTime(600);
	 * MemRow::getListId($module, $getListFunction);
	 *
	 * @var int
	 */
	private static $_listCacheLeftTime	= 86400;
	
	/**
	 * 单行数据对象缓存时间
	 *
	 * @var int
	 */
	private static $_rowCacheLeftTime	= 864000;
    
    /**
     * 设置id列表缓存时间
     *
     * @param int $leftTime	缓存秒数
     */
    public static function setListCacheTime($leftTime)
    {
    	self::$_listCacheLeftTime = $leftTime;
    }
    
    /**
     * 设置单行数据对象缓存时间
     *
     * @param int $leftTime	缓存秒数
     */
    public static function setRowCacheTime($leftTime)
    {
    	self::$_rowCacheLeftTime = $leftTime;
    }
    
     /**
     * 获取id列表缓存时间
     *
     * @return int
     */
    public static function getListCacheTime()
    {
    	return self::$_listCacheLeftTime;
    }
    
    /**
     * 获取单行数据对象缓存时间
     *
     * @return int
     */
    public static function getRowCacheTime()
    {
    	return self::$_rowCacheLeftTime;
    }

	/**
	 * 获取模块内id的缓存数据
	 * 
	 * mget为获取行数据缓存的唯一接口
	 * 
	 * getRowFunctio返回结果里必须包含名字为id的主键字段,
	 * 主键名称不是id的可以通过别名查询: SELECT group_id as id, group_id, group_name FROM table WHERE group_id IN(1,2,3,4,5);
	 *
	 * @param  string $module			模块名 如:friends, blog。 如果是子模块用下划线分割如friend_group, blog_category, cite_group_wow。 
	 * @param  int|array $id			单个id 或 包含多个id的数组			
	 * @param  array $getRowFunction	获取row数据的function	
	 * @return array
	 */
	public static function mget($module, $id, $getRowFunction,$flush = false)
	{
		if (is_array($id)) {
			return self::_getRowsCache($module, $id, $getRowFunction);
		} else {
			return self::_getRowCache($module, $id, $getRowFunction,$flush);
		}
	}
	/**
	 * 获取单个key的缓存的接口
	 *
	 * @param string $module
	 * @param array $params
	 * @param array $getRowFunction
	 * @return string
	 */
	
	public static function fget($module, $params, $getRowFunction){
		return self::_getFieldCache($module, $params, $getRowFunction);
	}
	
	/**
	 * int类型缓存自增或自减
	 *
	 * @param string $fieldKey
	 * @param array $getFieldFunction
	 * @param string $type
	 * @param int $value
	 * @param int $default
	 * @return int
	 */
	public static function crementField($fieldKey, $getFieldFunction, $type = 'incr', $value = 1, $default = 0){
		$result = null;
		if (!self::$_closeCache)
			$result = parent::get($fieldKey);
		if ( (null === $result) || (false === $result) ) {
			list($con, $act, $args) = $getFieldFunction;
			if (null !== ($dbRes = call_user_func_array(array($con, $act), $args))) {
				$result = $dbRes;
			}else{
				$result = $default;
			}
			if ($type == 'incr'){
				$result += $value;
			}else if ($dbRes - $value >= 0){
				$result -= $value;
			}
			self::$_closeCache || parent::set($fieldKey, $result, self::$_rowCacheLeftTime);
		}else{
			$_mem = new Mem();
			if ($type == 'incr'){
				$result = $_mem->incr($fieldKey, $value);
			}else{
				$result = $_mem->decr($fieldKey, $value);
			}
		}
		return $result;
	}
	
	/**
	 * 获取单个key缓存数据
	 *
	 * @param string $module
	 * @param int $id
	 * @param array $getRowFunction
	 * @return array
	 */
	private static function _getFieldCache($module, $params, $getFieldFunction)
	{
		$key = self::genRowKey($module, implode('_', $params));
		$result = null;
		if (!self::$_closeCache)
			$result = parent::get($key);
		
		if ( (null === $result) || (false === $result) ) {
			list($con, $act) = $getFieldFunction;
			if (null !== ($result = call_user_func_array(array($con, $act), (array)$params))) {
				self::$_closeCache || parent::set($key, $result, self::$_rowCacheLeftTime);
			}
		}
		return $result;
	}
	
	/**
	 * 获取单个key缓存数据
	 *
	 * @param string $module
	 * @param int $id
	 * @param array $getRowFunction
	 * @return array
	 */
	private static function _getRowCache($module, $id, $getRowFunction,$flush=false)
	{
		$key = self::genRowKey($module, $id);
		$result = null;
		if (!self::$_closeCache)
			$result = parent::get($key);
		
		if ( (null === $result) || (false === $result) || $flush==true ) {
			if (null !== ($result = self::_getRowFromDb($getRowFunction, $id))) {
				$result = array_shift($result);//mysql IN 查询单条记录弹出第一条
				self::$_closeCache || parent::set($key, $result, self::$_rowCacheLeftTime);
			}
		}
		return $result;
	}
	
	/**
	 * 获取多个key缓存数据
	 *
	 * @param string $module
	 * @param array $id
	 * @param array $getRowFunction
	 * @return array
	 */
	private static function _getRowsCache($module, $id, $getRowFunction)
	{
		//生成hash对应关系
		$hashKeys = array();
		foreach ($id as $mid) {
			$hashKeys[$mid] = self::genRowKey($module, $mid);
		}
	
		//获取命中的和未命中的cache数据
		list($hits, $misses) = self::$_closeCache ? array(array(), array_flip($hashKeys)) : parent::getMulti($hashKeys);
//		print_r($misses);	
		if ($misses) {//print_r($misses);
			if (null !== ($missesRows = self::_getRowFromDb($getRowFunction, $misses))) {
				$missesRows = self::_combineHashArray($hashKeys, $missesRows);
				self::$_closeCache || parent::setMulti($missesRows, self::$_rowCacheLeftTime);
				$hits = array_merge($hits, $missesRows);//合并未命中数据到命中数据	
			}
		}
	
		//按该页要显示的id排序
		$result = array();
		foreach ($hashKeys as $mid => $hashKey) {
			if (array_key_exists($hashKey, $hits) && $hits[$hashKey]) {
				$result[] = $hits[$hashKey];
			}
		}
		return $result;
	}
	
	/**
	 * 获取未命中数据
	 *
	 * @param array $getRowFunction
	 * @param array|int $misses
	 * @return array|null
	 */
	private static function _getRowFromDb($getRowFunction, $misses)
	{
		$misses = (array) $misses;
		
		//get row function parameter
		$params = array();
		if (count($getRowFunction) == 3) {
			list($con, $act, $arg) = $getRowFunction;
			if ($arg) $params = $arg;
		} else {
			list($con, $act) = $getRowFunction;
		}
			
		//$params 最后一个参数 为要查询的id列表的数组
		$params[] = array_values($misses);
		
		//查询未命中数据
		return call_user_func_array(array($con, $act), (array)$params);
	}
	
	/**
	 * 合并数组
	 *
	 * @param array $hashKey
	 * @param array $dataArray	必须包含字段名为 id 的主键
	 * @return array
	 */
	private static function _combineHashArray($hashKey, $dataArray)
	{
		$result = array();
		foreach ($dataArray as $k => $v) {
			$_key = 'id';
			if(!array_key_exists('id',$v) && array_key_exists('uid',$v)){
				$_key = 'uid';
			}
			$result[$hashKey[$v[$_key]]] = $v;
		}
		return $result;
	}
	
	/**
	 * 分页获取数据
	 *
	 * @param string $module	模块名
	 * @param int $currPage		当前页
	 * @param int $pageSize		每页显示数
	 * @param array $function	获取列表id和id对应数据的方法该处需提供获取id列表和单行数据的回调函数
	 * 							array(
	 * 								array($this->_mod, 'getListId', array($uid, $friend)), //getListId 后面的参数必须以array形式提供，会直接按顺序传递给getListId。
	 * 								array($this->_mod, 'getRow')//getRow必须使用in查询
	 * 							)
	 * @return array
	 */
	public static function getPageList($module, $currPage, $pageSize, $function)
	{
		$hits = $pageInfo = array();
		
		//getlist and getrow function
		list($getListFunction, $getRowFunction) = $function;
		
		//get list id
		if (!($listId = self::getListId($getListFunction))) {
			return array($hits, $pageInfo);
		}
		
		//page list id and page info
		list($pageId, $pageInfo) = self::getPageListId($listId, $currPage, $pageSize);
		
		$result = self::mget($module, $pageId, $getRowFunction);
		
		return array($result, $pageInfo);
	}
	
	/**
	 * 获取分页显示的ID和分页数据
	 *
	 * @param array $listId
	 * @param int $currPage
	 * @param int $pageSize
	 * @return array
	 */
	public static function getPageListId($listId, $currPage, $pageSize)
	{
		$listId		= (array) $listId;
		$offset 	= ($currPage - 1) * $pageSize;
		$count 		= count($listId);
		$totalPage  = ceil($count / $pageSize);
		$pageListId = array_slice($listId, $offset, $pageSize);
		
		return array(
			$pageListId,
			array('COUNT' => $count, 'PAGE' => $currPage, 'PAGESIZE' => $pageSize, 'PAGECOUNT' => $totalPage)
		);
	}
	
	/**
	 * 获取Id列表
	 *
	 * @param string $module	模块名
	 * @param array $function	获取列表id和id对应数据的方法: array($this->_mod, 'getListId'),
	 * @param bool $returnKey	是否返回列表cache_key
	 * @return array			array(1287, 5738, 92826)
	 */
	public static function getListId($getListFunction, $limit = 0, $returnKey = false)
	{
		//get list id key
		$key = self::genListKey($getListFunction);
		if (self::$_closeCache || (false === ($listId = parent::get($key))) ) {//echo 'no cache id list';
			list($con, $act, $arg) = $getListFunction;
			if (null === ($listId = call_user_func_array(array($con, $act), (array)$arg))) return array();
			self::$_closeCache || parent::set($key, $listId, self::$_listCacheLeftTime);
		}
		
		if ($limit) {
			$listId = array_slice($listId, 0, $limit);
		}
		
		return $returnKey ? array($key, $listId) : $listId;
	}
	
	/**
	 * 生成单条信息cache key
	 *
	 * @param string $module
	 * @param int $id
	 * @return string
	 */
	public static function genRowKey($module, $id)
	{
		return parent::genkey(array($id), $module);
	}
	
	/**
	 * 生成list id cache key (供外部调用)
	 *
	 * @param array $params		array('friends', 'data', 'getListId', $uid, 1);
	 * @return string
	 */
	public static function genListKeyByResource($params)
	{
		return parent::genkey($params);
	}
	
	/**
	 * 生成list id cache key
	 *
	 * @param array $getListFunction	array(Model::factory('friends_model_data'), 'getListId', array($uid, 1))
	 * 									echo MemRow::genListKey(array(__CLASS__,'getGroupedFriends', array($uid, 1)));
	 * @return string
	 */
	public static function genListKey($getListFunction)
	{
		list($model, $act, $arg) = $getListFunction;

		if (is_object($model)) $model = get_class($model);
		
		$model = str_replace('_model_', '_', strtolower($model));

		$params = array_merge(array($model, $act), (array)$arg);

		return parent::genkey($params);
	}
	
	/**
	 * 获取一个key值
	 *
	 * @param str|array $key
	 * @return str|array
	 */
	public static function getCache($key)
	{
		if (self::$_closeCache) return false;
		return parent::get($key); 
	}
	
	/**
	 * mset 别名
	 *
	 * @param str $key
	 * @param mixed $data
	 * @param int $leftTime
	 * @return bool
	 */
	public static function setCache($key, $data, $leftTime = null)
	{
		return self::mset($key, $data, $leftTime);
	}

	/**
	 * Mdel别名
	 *
	 * @param str $key
	 * @return bool
	 */
	public static function removeCache($key)
	{
		return self::mdel($key);
	}
	
	/**
	 * 设置一个key值
	 *
	 * @param string $key
	 * @param mixed $data
	 * @param int $leftTime 默认值为self::$_rowCacheLeftTime
	 * @return bool
	 */
	public static function mset($key, $data, $leftTime = null)
	{
		if (null === $leftTime) $leftTime = self::$_rowCacheLeftTime;
		return self::$_closeCache || parent::set($key, $data, $leftTime);
	}
	
	/**
	 * 删除一个key值
	 *
	 * @param string $key
	 * @return bool
	 */
	public static function mdel($key)
	{
		return self::$_closeCache || parent::remove($key);
	}
	
	/**
	 * 增加list id
	 *
	 * @param int|array $id				要增加的单个ID
	 * @param string $getListFunction	缓存id列表的回调函数
	 * @return bool
	 */
	public static function incrList($id, $getListFunction)
	{
		return self::_modifyListId($id, $getListFunction, 'incr');
	}
	
	/**
	 * 减少list id
	 *
	 * @param int|array $id				要减少的单个ID
	 * @param string $getListFunction	缓存id列表的回调函数
	 * @return bool
	 */
	public static function decrList($id, $getListFunction)
	{
		return self::_modifyListId($id, $getListFunction, 'decr');
	}
	
	/**
	 * 修改list id
	 *
	 * @param int|array $id
	 * @param array $getListFunction
	 * @return bool
	 */
	private static function _modifyListId($id, $getListFunction, $type)
	{
		//get list
		list($key, $listId) = self::getListId($getListFunction, 0, true);
		
		$listId = (array) $listId;
		
		if ('incr' == $type) {
			$listId = self::_incrListId($listId, $id);
		} else if ('decr' == $type) {
			$listId = self::_decrListId($listId, $id);
		}
		self::$_closeCache || parent::set($key, $listId, self::$_listCacheLeftTime);
		
		return $listId;
	}
	
	/**
	 * 增加id到list id
	 *
	 * @param array $listId
	 * @param int|array $id
	 * @return bool
	 */
	private static function _incrListId($listId, $id)
	{
		if (!$id) return $listId;
		
		$tem = array_flip($listId);
		
		$id = (array) $id;
		
		foreach ($id as $k => $v) {
			if (!array_key_exists($v, $tem)) array_unshift($listId, $v);
		}
		
		unset($tem);
		return $listId;
	}
	
	/**
	 * 移除id从list id
	 *
	 * @param array $listId
	 * @param int $id
	 * @return bool
	 */
	private static function _decrListId($listId, $id)
	{
		if (!$id) return $listId;
		
		$tem = array_flip($listId);
		
		$id = (array) $id;
		
		foreach ($id as $k => $v) {
			if (array_key_exists($v, $tem)) unset($listId[$tem[$v]]);
		}
		
		unset($tem);
		return array_values($listId);
	}
}
